import "../../lib/ramda.js";
import utils from "../../lib/utils.js";
import fui from "../../lib/fui.js";
import ux from "./ux.js";
import game from "./game.js";

const Consts = {
    FPS:20,
    DEFAULT_KVS: utils.redis.DEFAULT_KVS("ff", {
        "local-port": 8093,
        "uct-times": 16384,
        "level": 1,
    }),
}

class GameLog {
    constructor() {
        this.reset()
    }
    reset() {
        this._states = []
        this._cursor = -1
    }
    step_nums() {return this._states.length}
    to_first() {
        if (this.step_nums() > 0) {
            this._cursor = 0
            return this._states[0]
        } else {
            return undefined
        }
    }
    to_last() {
        if (this.step_nums() > 0) {
            this._cursor = this.step_nums()-1
            return this._states[this._cursor]
        } else {
            return undefined
        }
    }
    undo() {
        if (this._cursor > 0) {
            this._cursor -= 1
            return this._states[this._cursor]
        } else {
            return undefined
        }
    }
    redo() {
        if (1+this._cursor < this.step_nums()) {
            this._cursor += 1
            return this._states[this._cursor]
        } else {
            return undefined
        }
    }
    push(game_state) {
        this._cursor += 1
        this._states.splice(this._cursor)
        this._states.push(new game.State(game_state))
        return true
    }
}

class EngineWorker {
    constructor(name, eventp) {
        this._msg_fns = {}
        const worker = new Worker(`./${name}.js`, {type: "module"})
        worker.onmessage = (e)=>{
            const msg = e.data
            const tokens = msg.split(' ')
            if (tokens.length > 0) {
                const head = tokens[0]
                if (this._msg_fns.hasOwnProperty(head)) {
                    this._msg_fns[head](...(tokens.slice(1)))
                } else {
                    console.log("[Unknown-Message]", msg)
                }
            }
        }
        worker.onerror = (e)=>{
            console.log(`AI-Error(${e.filename}[${e.lineno}]): ${e.message}`, e)
            eventp.on_error(e)
        }
        this._worker = worker
    }
    bind_message(head, fn) {
        this._msg_fns[head] = fn
        return this
    }
    send(...tokens) {
        const text = R.map(String, tokens).join(' ')
        this._worker.postMessage(text)
        return this
    }
    restart() {
        this._worker.postMessage("reset")
        return this
    }
}

class State {
    constructor() {
        this.kvs = utils.redis.make_kvs("ff")
        this._game_state = new game.State()
        this._game_log = new GameLog()
        this._params = new Map([
            ["uct-times", this.kvs.text("uct-times")],
            ["level", this.kvs.text("level")],
        ])
        this._engine_worker = new EngineWorker("ai-engine", {
            on_error(){
                console.log(this)
            },
        })
        this._engine_worker.bind_message("ready", ()=>{
            for (const kv of this._params.entries()) {
                this._engine_worker.send("set", kv[0], kv[1])
            }
        }).bind_message("step", (_loc)=>{
            if (this._ai_in_think) {
                const loc = Number(String(_loc))
                if (! this.on_down(loc, true)) {
                    this._ai_players[this.get_pcur()] = false
                }
                this._ai_in_think = false
            }
        }).bind_message("status", (...a)=>{
            if (this._ai_in_think) {
                const text = a.join(' ')
                this._ai_status[this.get_pcur()] = text
            }
        }).bind_message("say", (...a)=>{
            if (this._ai_in_think) {
                const text = a.join(' ')
                this._ai_said[this.get_pcur()] = text
            }
        }).bind_message("ok", (...a)=>{
            console.log("[engine]", a.join(' '))
        })
        this._reset_ai_data()
    }
    _reset_ai_data() {
        this._ai_in_think = false
        this._ai_players = [false, false, false]
        this._ai_status = ["", "", ""]
        this._ai_said = ["", "", ""]
    }
    reset() {
        this._game_state.reset()
        this._game_log.reset()
        this._engine_worker.restart()
        this._reset_ai_data()
        console.clear()
    }
    param(name, to=undefined) {
        if (R.is(String,to)) {
            if (this._params.has(name)) {
                this._params.set(name, to)
                this.kvs.text(name, to)
                this._engine_worker.send("set", name, to)
            }
        }
        return this._params.get(name)
    }
    ai_player(pcur, enable=undefined) {
        if (R.is(Boolean, enable)) {
            this._ai_players[pcur] = enable || (this.ai_player(pcur) && this._ai_in_think && (pcur == this.get_pcur()))
        } else {
            return this._ai_players[pcur]
        }
    }
    on_frame() {
        if (this._ai_in_think) {return}
        if (this._game_state.has_over()) {return}
        //AiManager.send("used-engine")
        const pcur = this.get_pcur()
        if (this.ai_player(pcur)) {
            this._ai_status[pcur] = ""
            this._engine_worker.send("step", game.piece_to_str(pcur), this.get_board().to_str())
            this._ai_in_think = true
        }
    }
    on_down(loc, from_ai=false) {
        if ((! this._ai_in_think) || from_ai) {
            if (this._game_state.down(loc)) {
                this._game_log.push(this._game_state)
                return true
            }
        }
        return false
    }
    on_extra(name) {
        const random_game = (min_np, max_np)=>{
            this._to_state(game.random_state(min_np, max_np))
            this._game_log.reset()
            this._game_log.push(this._game_state)
        }
        const fns = {
            "random-game": ()=>{random_game(0, game.BOARD_LEN)},
            "random-game-mid": ()=>{random_game(50, game.BOARD_LEN-35)},
            "random-game-end": ()=>{random_game(game.BOARD_LEN-35, game.BOARD_LEN-15)},
        }
        if (fns.hasOwnProperty(name)) {
            (fns[name])()
        }
    }
    on_cmd_reset() {
        if (! this._ai_in_think) {
            this.reset()
        }
    }
    _to_state(game_state) {
        if ((! this._ai_in_think) && R.is(game.State, game_state)) {
            this._reset_ai_data()
            this._game_state.reset(game_state)
        }
    }
    on_cmd_load_first() {
        this._to_state(this._game_log.to_first())
    }
    on_cmd_load_last() {
        this._to_state(this._game_log.to_last())
    }
    on_cmd_undo() {
        this._to_state(this._game_log.undo())
    }
    on_cmd_redo() {
        this._to_state(this._game_log.redo())
    }
    get_record_top() {
        return this._game_state.record_top()
    }
    get_board() {
        return this._game_state.board()
    }
    get_end_piece() {
        return this._game_state.end_piece()
    }
    get_pcur() {
        return this._game_state.pcur()
    }
    get_ai_status(pcur) {
        return this._ai_status[pcur]
    }
    get_ai_said(pcur) {
        return this._ai_said[pcur]
    }
}

;(() => {
    utils.redis.init({
        on_key_notfound(k){return Consts.DEFAULT_KVS[k]}
    })
    const io_fns = new State()
    const clock = new fui.Clock();
    clock.frame_join(()=>{io_fns.on_frame()}).frame_join(ux.init(io_fns)).start(Consts.FPS)
})()
